---
title: node 爬虫
date: 2023-09-19 22:10:43 
images: ['https://kodo.huixiangwuyou.com/blog/images/nodecrawl.png']
summary: '爬虫不是python的独有功能，其实类似Java、C++、node这类后端也是支持的。现在就是要做一个爬虫，然后把数据存到数据库中。互联网的大数据时代的来临，网络爬虫也成了互联网中一个重要行业，它是一种自动获取网页数据信息的爬虫程序，是网站搜索引擎的重要组成部分。通过爬虫，可以获取自己想要的相关数据信息，让爬虫协助自己的工作，进而降低成本，提高业务成功率和提高业务效率。'
tags: 
 - Node
---



### 爬虫的定义
爬虫分为通用爬虫和聚焦爬虫两大类，前者的目标是在保持一定内容质量的情况下爬取尽可能多的站点，比如百度这样的搜索引擎就是这种类型的爬虫，爬虫通常从一个或多个 URL 开始，在爬取的过程中不断将新的并且符合要求的 URL 放入待爬队列，直到满足程序的停止条件。而我们日常见到的爬虫基本为聚焦爬虫，目标是在爬取少量站点的情况下尽可能保持精准的内容质量。典型的比如抢票软件，就是利用爬虫来登录售票网络并爬取信息，从而辅助商业。
**步骤**
- 确定爬取的目标网站：确定要爬取的网站的 URL 地址。
- 发送 HTTP 请求：使用 HTTP 协议向目标网站发送请求，获取网页内容。
- 解析网页内容：使用 HTML 解析器解析网页内容，提取需要的数据。



### 项目搭建

#### 思路逻辑
- 递归爬取：从首页开始，提取所有链接，并递归爬取这些链接。
  - 从首页开始，提取页面中的所有链接。
  - 使用new URL(href, baseUrl).href将相对路径转换为绝对路径。
  - 只爬取同一域名下的链接（通过startsWith(baseUrl)判断）。
- 去重：避免重复爬取相同的页面。
  - 使用Set（visitedUrls）记录已访问的 URL，避免重复爬取。
- 并发控制：限制并发请求数量，避免对目标服务器造成过大压力。
  - 使用p-limit限制并发请求数量，避免对目标服务器造成过大压力。
- 数据存储：将爬取的数据存储到文件或数据库中。
  - 将爬取的数据存储到allData数组中。
  - 最后将数据保存到data.json文件中。
- 错误处理：处理网络请求失败、超时等问题。

#### 安装依赖
```bash
npm install axios cheerio puppeteer-core p-limit@4.0.0 -S
```
- axios：用于发送HTTP请求。
- cheerio：是一个用于解析HTML文档的库，它不需要执行JavaScript代码，在服务器端快速获取HTML页面的DOM结构。使用Cheerio可以方便地进行DOM操作、数据抓取和爬虫等任务。
- puppeteer：是一个Node.js库，用于控制Headless Chrome或Chromium浏览器(无头浏览器)，可以进行自动化测试、屏幕截图、数据采集等任务。Puppeteer可以模拟用户的操作行为，例如点击、输入、滚动等等。puppeteer-core是一个轻量级的 Puppeteer 版本，不会自动下载 Chrome。
- p-limit：用于限制并发请求数量。


#### 代码
```js
const axios = require('axios')
const cheerio = require('cheerio')
const puppeteer = require('puppeteer-core');
const {URL} = require('url')
// p-limit 本地下载失败
// const pLimit = require('p-limit')

const baseUrl = 'https://blog.huixiangwuyou.com/'

// 限制并发请求数量
// const limit = pLimit(5) // 同时最多 5 个请求

// 目标网站的根域名
const visitedUrls = new Set() // 用于去重
const allData = [] // 存储爬取的数据


/**
 * 爬取单个页面
 * @param {string} url - 要爬取的页面 URL
 */
async function crawlPage(url,depth=0,maxDepth=5) {
 if (depth > maxDepth) return;
	if (visitedUrls.has(url)) return // 如果已经访问过，跳过
	visitedUrls.add(url) // 标记为已访问

	try {
		console.log(`Crawling: ${url}`)
  let html = ''
  // 根据是否是spa页面，选择不同的方式获取html
  if(url.indexOf('blog')>-1){
    const browser = await puppeteer.launch({ 
     headless: true ,
     executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe', // 指定 Chrome 的路径
     // userDataDir: '/path/to/cache', 
     ignoreHTTPSErrors: true, // 忽略 HTTPS 错误
     timeout: 30000, // 设置超时时间为 30 秒
    });
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2' });
  
    // 获取页面内容
    html = await page.content();
    await browser.close();
  }else{
   const response = await axios.get(url)
   html = response.data
  }
  // 使用 cheerio 解析 HTML
		const $ = cheerio.load(html)

		// 提取页面中的数据（示例：提取标题和正文）
		const title = $('title').text()
		const body = $('body').text().trim()
		allData.push({url, title, body})

		// 提取页面中的所有链接
		const links = []
		$('a').each((index, element) => {
			const href = $(element).attr('href')
			if (href) {
				const absoluteUrl = new URL(href, baseUrl).href // 转换为绝对 URL
				if (absoluteUrl.startsWith(baseUrl)) {
					// 只爬取同一域名下的链接
					links.push(absoluteUrl)
				}
			}
		})

		// 递归爬取链接
		// await Promise.all(links.map(link => limit(() => crawlPage(link,depth + 1, maxDepth))))
		await Promise.all(links.map(link => crawlPage(link,depth + 1, maxDepth)))
	} catch (error) {
		console.error(`Failed to crawl ${url}:`, error.message)
	}
}

/**
 * 启动爬虫
 */
async function startCrawling() {
	await crawlPage(baseUrl) // 从首页开始爬取
	console.log('Crawling completed!')
	console.log('Total pages crawled:', visitedUrls.size)

	// 将数据保存到文件（可选）
	const fs = require('fs')
	fs.writeFileSync('data.json', JSON.stringify(allData, null, 2))
	console.log('Data saved to data.json')
}

// 启动爬虫
startCrawling()

```
[项目代码](https://gitee.com/miss-tree/crawler.git)
<br/>


#### 非a标签的处理
爬虫默认是根据a标签来爬取的，但是有些页面不是a标签，而是其他标签，比如div、span、p等等，这种情况下，我们可以使用puppeteer来获取页面的html，
然后在代码中处理点击事件的元素：
```js
const puppeteer = require('puppeteer-core');

(async () => {
  // 启动浏览器
  const browser = await puppeteer.launch({ headless: false }); // headless: false 可以看到浏览器操作
  const page = await browser.newPage();

  // 访问目标页面
  const url = 'https://example.com'; // 替换为目标 SPA 的 URL
  await page.goto(url, { waitUntil: 'networkidle2' });

  // 等待目标元素加载
  await page.waitForSelector('.clickable-button'); // 替换为目标按钮的选择器

  // 模拟点击按钮
  await Promise.all([
    page.waitForNavigation({ waitUntil: 'networkidle2' }), // 等待跳转完成
    page.click('.clickable-button'), // 点击按钮
  ]);

  // 获取跳转后的页面内容
  const content = await page.content();
  console.log(content);

  // 关闭浏览器
  await browser.close();
})();

```

#### 注意事项
在执行爬虫任务的时候，若是电脑没有单独显卡支持的，执行任务会消耗cpu，导致电脑卡顿，

### robots协议
Robots协议（也称为爬虫协议、机器人协议等）的全称是“网络爬虫排除标准”（Robots Exclusion Protocol），网站通过Robots协议告诉搜索引擎哪些页面可以抓取，哪些页面不能抓取。

Robots协议是一个文本文件，通常位于网站的根目录下，文件名为robots.txt。该文件包含了一系列指令，告诉搜索引擎哪些页面可以被抓取，哪些页面不能被抓取。
当一个搜索蜘蛛访问一个站点时，它会首先检查该站点根目录下是否存在robots.txt，如果存在，搜索机器人就会按照该文件中的内容来确定访问的范围；
如果该文件不存在，所有的搜索蜘蛛将能够访问网站上所有没有被口令保护的页面。百度官方建议，仅当您的网站包含不希望被搜索引擎收录的内容时，才需要使用robots.txt文件。如果您希望搜索引擎收录网站上所有内容，请勿建立robots.txt文件。

### 反爬虫技术
反爬虫的定义和意义，限制爬虫程序访问服务器资源和获取数据的行为称为反爬虫。爬虫程序的访问速率和目的与正常用户的访问速率和目的是不同的，大部分爬虫会无节制地对目标应用进行爬取，这给目标应用的服务器带来巨大的压力。爬虫程序发出的网络请求被运营者称为“垃圾流量”。开发者为了保证服务器的正常运转或降低服务器的压力与运营成本，不得不使出各种各样的技术手段来限制爬虫对服务器资源的访问。反爬虫技术是网站或应用程序为了防止恶意爬虫程序获取其数据而采用的一系列手段。以下是一些常见的反爬虫技术：
- IP 限制：限制同一 IP 地址的访问次数，防止单个 IP 地址的请求过于频繁。
- User-Agent 限制：限制特定 User-Agent 的访问次数，防止特定爬虫程序的请求过于频繁。
- 图片验证码：在某些情况下，为了防止爬虫程序获取数据，网站或应用程序会要求用户滑动图片或者输入顺序图片验证码。
- 验证码验证：网站或应用程序会对用户输入的验证码进行验证，以防止恶意爬虫程序获取数据。
